/*
Copyright (c) [2019年5月1日] [吴超]
[MBT_studio] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
		 http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/
#pragma once

#include <Windows.h>


#define DIRECTINPUT_VERSION 0x800


#include <dinput.h>
//#include <afxtempl.h>

#pragma comment(lib, "dinput8.lib")
#pragma comment(lib, "DXguid.Lib")





class CDInputKeyboard {
	#define BUFFERCOUNT 256
	LPDIRECTINPUT         lpDirectInput;
	LPDIRECTINPUTDEVICE   lpdiKeyboard;
	BOOL                  KeyboardAcquired;
	BOOL                  KeyboardFound;
	//CTypedPtrArray <CPtrArray, LPDIDEVICEINSTANCE> m_KeyboInstance_Array;
	std::vector<LPDIDEVICEINSTANCE>		m_KeyboInstance_Array;

	HWND                m_hMainWindow;


public:
	////////////////////////////////////////////////
	CDInputKeyboard() {
		m_hMainWindow = NULL;
		lpDirectInput = NULL;
		lpdiKeyboard = NULL;
		KeyboardAcquired = 0;
		KeyboardFound = 0;

		m_KeyboInstance_Array.clear();
		m_KeyboInstance_Array.resize(10);
	}

	virtual ~CDInputKeyboard() {
		if (lpdiKeyboard) {
			lpdiKeyboard->Unacquire();
			lpdiKeyboard->Release();
			lpdiKeyboard = NULL;
		}

		if (lpDirectInput) {
			lpDirectInput->Release();
			lpDirectInput = NULL;
		}

		RemoveGUID();
	}
	
	virtual BOOL InitInput(HWND hWnd, HINSTANCE hInstance) {
		assert(hWnd);
		
		m_hMainWindow = hWnd;
		auto error = DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&lpDirectInput, NULL);
		if (error != DI_OK) {
			lpDirectInput = NULL;
			//OutputDebugString(_T("Failed to create DirectInput object./n"));
			return FALSE;
		}
		return TRUE;
	}
	
	INT_PTR EnumKeyboard() {
		RemoveGUID();
		if (lpDirectInput) {
			// enumerate devices so we can get the GUIDs
			if (lpDirectInput->EnumDevices(0, DIEnumDevicesProc, this, DIEDFL_ALLDEVICES) != DI_OK) {
				//OutputDebugString(_T("Could not enumerate devices./n"));
				std::cout << "Could not enumerate devices" << std::endl;
			}
		}
		return m_KeyboInstance_Array.size();
	}
	
	BOOL Unacquire() {
		if (lpdiKeyboard) {
			lpdiKeyboard->Unacquire();
			lpdiKeyboard->Release();
			lpdiKeyboard = NULL;
		}
		KeyboardAcquired = false;
		return TRUE;
	}
	
	BOOL Acquire(INT_PTR nPos = -1) {
		Unacquire();
		
		if (lpDirectInput == NULL) {
			std::cout<<"lpDirectInput is NULL./n"<<std::endl;
			return FALSE;
		}
		
		GUID KeyboardGUID = GUID_SysKeyboard;
		if (nPos >= 0 && nPos < int32(m_KeyboInstance_Array.size()) ) {
			KeyboardGUID = m_KeyboInstance_Array[nPos]->guidInstance;
		}
		// try to create keyboard device
		//GUID_SysKeyboard 
		if (lpDirectInput->CreateDevice(KeyboardGUID, &lpdiKeyboard, NULL) != DI_OK){
			std::cout << "Failed to create keyboard device./n" << std::endl;
			Unacquire();
			return FALSE;
		}
		// set kbd cooperative level
		if (lpdiKeyboard->SetCooperativeLevel(m_hMainWindow, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE) != DI_OK) {
			std::cout << "Failed to set keyboard cooperative level./n" << std::endl;
			Unacquire();
			return FALSE;
		}
		// set kbd data format
		if (lpdiKeyboard->SetDataFormat(&c_dfDIKeyboard) != DI_OK) {
			std::cout << "Failed to set keyboard data format./n" << std::endl;
			Unacquire();
			return FALSE;
		}
		// set kbd buffer size
		DIPROPDWORD dipdw = { 0 };
		dipdw.diph.dwSize = sizeof(DIPROPDWORD);
		dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
		dipdw.diph.dwObj = 0;
		dipdw.diph.dwHow = DIPH_DEVICE;
		dipdw.dwData = BUFFERCOUNT * sizeof(DIDEVICEOBJECTDATA); 
		
		if (lpdiKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) != DI_OK) {
			std::cout << "Failed to set keyboard buffer size./n"<<std::endl;
			Unacquire();
			return FALSE;
		}
		// try to acquire the keyboard
		if (lpdiKeyboard->Acquire() != DI_OK) {
			std::cout << "Failed to acquire the keyboard./n"<<std::endl;
			Unacquire();
			return FALSE;
		}
		KeyboardAcquired = TRUE;
		return TRUE;
	}

	bool f_获取设备(S_结构指针* 设备, const E_输入设备类型预设& 设备类型预设) {
		if (lpDirectInput == NULL) {
			std::cout << "lpDirectInput is NULL./n" << std::endl;
			return FALSE;
		}

		LPDIRECTINPUTDEVICE& lpdi = *(LPDIRECTINPUTDEVICE*)设备;



		GUID guid = GUID_SysKeyboard;
		DIDATAFORMAT Format;

		switch (设备类型预设) {
		case E_输入设备类型预设::e_In键盘:
			guid = GUID_SysKeyboard;
			Format = c_dfDIKeyboard;
			break;
		case E_输入设备类型预设::e_In鼠标:
			guid = GUID_SysMouse;
			Format = c_dfDIMouse;
			break;
		case E_输入设备类型预设::e_In键盘扩展1:
			guid = m_KeyboInstance_Array[1]->guidInstance;
			Format = c_dfDIMouse;
			break;
		default:
			break;
		}

		// try to create keyboard device
		//GUID_SysKeyboard 
		if (lpDirectInput->CreateDevice(guid, &lpdi, NULL) != DI_OK) {
			std::cout << "Failed to create keyboard device./n" << std::endl;
			f_释放设备(设备);
			return FALSE;
		}
		// set kbd cooperative level
		if (lpdi->SetCooperativeLevel(m_hMainWindow, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE) != DI_OK) {
			std::cout << "Failed to set keyboard cooperative level./n" << std::endl;
			f_释放设备(设备);
			return FALSE;
		}
		// set kbd data format
		if (lpdi->SetDataFormat(&Format) != DI_OK) {
			std::cout << "Failed to set keyboard data format./n" << std::endl;
			f_释放设备(设备);
			return FALSE;
		}
		// set kbd buffer size
		DIPROPDWORD dipdw = { 0 };
		dipdw.diph.dwSize = sizeof(DIPROPDWORD);
		dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
		dipdw.diph.dwObj = 0;
		dipdw.diph.dwHow = DIPH_DEVICE;
		dipdw.dwData = BUFFERCOUNT; // * sizeof(DIDEVICEOBJECTDATA);
		if (lpdi->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) != DI_OK) {
			std::cout << "Failed to set keyboard buffer size./n" << std::endl;
			f_释放设备(设备);
			return FALSE;
		}
		// try to acquire the keyboard
		if (lpdi->Acquire() != DI_OK) {
			std::cout << "Failed to acquire the keyboard./n" << std::endl;
			f_释放设备(设备);
			return FALSE;
		}
		KeyboardAcquired = TRUE;


		return TRUE;
	}

	void f_释放设备(S_结构指针* 设备) {
		LPDIRECTINPUTDEVICE& lpdi = *(LPDIRECTINPUTDEVICE*)设备;

		lpdi->Unacquire();
		lpdi->Release();
		lpdi = NULL;
		
	}

	
	BOOL ReacquireInput(void) {
		//the keyboard
		if (lpdiKeyboard != NULL) {
			lpdiKeyboard->Acquire();
		}
		else {
		// keyboard device has not been created.
			return FALSE;
		}
		// if we get here, we are acquired again
		KeyboardAcquired = TRUE;
	
		return TRUE;
	}  // ReacquireInputDevices()
	
	//Reads the keyboard state
	BOOL PollKeyboard(int8* diKeys) {
		if (lpdiKeyboard && KeyboardAcquired) {
			if (lpdiKeyboard->GetDeviceState(256, &diKeys) == DI_OK) {
				if (diKeys[DIK_LWIN] & 0x80) /* Left Windows key */ {
				}
				if (diKeys[DIK_RWIN] & 0x80) /* Right Windows key */ {
				}
				return TRUE;  // success
			}
		}
		return FALSE;
	}

	void f_get鼠标状态(S_结构指针* 设备, S_结构指针 鼠标状态) {
		/*if (lpdiKeyboard->Acquire() != DI_OK) {
			return;
		}*/
		LPDIRECTINPUTDEVICE& lpdi = *(LPDIRECTINPUTDEVICE*)设备;

		if (lpdi->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)鼠标状态)) {
			//popMessage(TEXT("获取鼠标状态出错了"));
			std::cout << "获取鼠标状态出错了" << std::endl;
		}
	}

	void f_get键盘状态(S_输入环境* ctx) {
		/*if (lpdiKeyboard->Acquire() != DI_OK) {
			return;
		}*/
		LPDIRECTINPUTDEVICE lpdi = ((LPDIRECTINPUTDEVICE)(ctx->m_键盘设备));
		lpdi->GetDeviceState(256 * sizeof(uint8), ctx->m_Key.Keys);
	}
	
	HRESULT ReadKeyboardInput(void) {
		HRESULT hRes = DI_OK;
		if (KeyboardAcquired) {
			DIDEVICEOBJECTDATA  KbdBuffer[BUFFERCOUNT] = { 0 };
			DWORD dwItems = BUFFERCOUNT;
			hRes = lpdiKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), KbdBuffer, &dwItems, 0);
			if (hRes == DI_OK || hRes == DI_BUFFEROVERFLOW) {
				for (DWORD k = 0; k < dwItems; k++) {
					LPDIDEVICEOBJECTDATA lpdidod = &KbdBuffer[k];
					
					//映射到虚拟键
					TCHAR VKey = MapVirtualKey(lpdidod->dwOfs, 3); 
					//处理输入
					ProcessInputKey(VKey); 
				}
			}
			else if (hRes == DIERR_INPUTLOST) {
				ReacquireInput();
				hRes = S_FALSE;
			}
		}
		return hRes;
	}
	

protected:
	virtual BOOL ProcessInputKey(TCHAR tChar) {
		return false;
	}

	static BOOL CALLBACK DIEnumDevicesProc(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) {
		if (GET_DIDEVICE_TYPE(lpddi->dwDevType) == DI8DEVTYPE_KEYBOARD
			|| ((lpddi->dwDevType & DIDEVTYPE_HID) && GET_DIDEVICE_TYPE(lpddi->dwDevType) == DI8DEVTYPE_DEVICE))
		{
			LPDIDEVICEINSTANCE pNewdid = (LPDIDEVICEINSTANCE) new DIDEVICEINSTANCE;
			memcpy(pNewdid, lpddi, sizeof(DIDEVICEINSTANCE));
			//((CDInputKeyboard*)pvRef)->m_KeyboInstance_Array.Add(pNewdid);
			((CDInputKeyboard*)pvRef)->m_KeyboInstance_Array.push_back(pNewdid);
		}
		return DIENUM_CONTINUE;
	} // DIEnumDevicesProc

	void RemoveGUID() {
		KeyboardFound = 0;
		/*while (m_KeyboInstance_Array.GetSize() > 0) {
			delete m_KeyboInstance_Array.GetAt(0);
			m_KeyboInstance_Array.RemoveAt(0);
		}*/

		while (m_KeyboInstance_Array.size() > 0) {
			delete m_KeyboInstance_Array[0];
			m_KeyboInstance_Array.erase(m_KeyboInstance_Array.begin());
		}
	}

};



